module nade.block_map;

import derelict.opengl.gl;

import nade.coordinate;
import nade.color;


class BlockMap(int W, int H, int D) {
	static const VW = W + 1;
	static const VH = H*2 + 1;
	static const VD = D + 1;

	this()
	{
		_blocks = new Block[W][H][D];

		_vertices.length = VW*VH*VD*3;

		GLfloat* vi = _vertices.ptr;
		for(int z = 0;  z < VD;  z++)
		{
			for(int y = 0;  y < VH;  y++)
			{
				for(int x = 0;  x < VW;  x++)
				{
					*(vi++) = x;
					*(vi++) = y*0.5;
					*(vi++) = z;
				}
			}
		}

		fill(boundingBox, null);
	}

	void render(const Region3 box)
	{
		const nbox = box.normal.intersect(boundingBox);
		if(!nbox.isValid) return;

		glEnable(GL_TEXTURE_2D);

		foreach(z, layer; _blocks[nbox.from.z .. (nbox.to.z + 1)])
			foreach(y, row; layer[nbox.from.y .. (nbox.to.y + 1)])
				foreach(x, cell; row[nbox.from.x .. (nbox.to.x + 1)])
					if(cell !is null)
						cell.render(x + y*VW*2 + z*VW*VH);

		glDisableClientState(GL_VERTEX_ARRAY);
		glDisable(GL_TEXTURE_2D);
	}

	void fill(const Coord3 coord, Block block)
	{
		_blocks[coord.z][coord.y][coord.x] = block;
	}
	void fill(const Region3 box, Block block)
	{
		const nbox = box.normal;

		foreach(ref layer; _blocks[nbox.from.z .. (nbox.to.z + 1)])
			foreach(ref row; layer[nbox.from.y .. (nbox.to.y + 1)])
				foreach(ref cell; row[nbox.from.x .. (nbox.to.x + 1)])
					cell = block;
	}

	Region3 boundingBox()
	{
		return Region3(0, 0, 0, W - 1, H - 1, D - 1);
	}

	void sendVertex(uint i)
	{
		glVertex3fv(_vertices.ptr + i*3);
	}

	private {
		Block[W][H][]		_blocks;

		GLfloat[]			_vertices;
	}


	class Block {
		abstract void render(uint i);

		static uint innn(uint i) { return i; }
		static uint ipnn(uint i) { return i + 1; }
		static uint innp(uint i) { return i + VW*VH; }
		static uint ipnp(uint i) { return i + VW*VH + 1; }
		static uint inzn(uint i) { return i + VW; }
		static uint ipzn(uint i) { return i + VW + 1; }
		static uint inzp(uint i) { return i + VW*VH + VW; }
		static uint ipzp(uint i) { return i + VW*VH + VW + 1; }
		static uint inpn(uint i) { return i + VW*2; }
		static uint ippn(uint i) { return i + VW*2 + 1; }
		static uint inpp(uint i) { return i + VW*VH + VW*2; }
		static uint ippp(uint i) { return i + VW*VH + VW*2 + 1; }
	}


	class CubeBlock : Block {
		this(GLuint texture)
		{
			_texture = texture;
		}

		override void render(uint i)
		{
			glBindTexture(GL_TEXTURE_2D, _texture);

			glBegin(GL_QUADS);
			glNormal3f(-1, 0, 0);
			glTexCoord2f(0, 0); sendVertex(innn(i));
			glTexCoord2f(1, 0); sendVertex(innp(i));
			glTexCoord2f(1, 1); sendVertex(inpp(i));
			glTexCoord2f(0, 1); sendVertex(inpn(i));

			glNormal3f(1, 0, 0);
			glTexCoord2f(0, 1); sendVertex(ippn(i));
			glTexCoord2f(1, 1); sendVertex(ippp(i));
			glTexCoord2f(1, 0); sendVertex(ipnp(i));
			glTexCoord2f(0, 0); sendVertex(ipnn(i));

			glNormal3f(0, -1, 0);
			glTexCoord2f(0, 0); sendVertex(innn(i));
			glTexCoord2f(1, 0); sendVertex(ipnn(i));
			glTexCoord2f(1, 1); sendVertex(ipnp(i));
			glTexCoord2f(0, 1); sendVertex(innp(i));

			glNormal3f(0, 1, 0);
			glTexCoord2f(0, 1); sendVertex(inpp(i));
			glTexCoord2f(1, 1); sendVertex(ippp(i));
			glTexCoord2f(1, 0); sendVertex(ippn(i));
			glTexCoord2f(0, 0); sendVertex(inpn(i));

			glNormal3f(0, 0, -1);
			glTexCoord2f(0, 1); sendVertex(inpn(i));
			glTexCoord2f(1, 1); sendVertex(ippn(i));
			glTexCoord2f(1, 0); sendVertex(ipnn(i));
			glTexCoord2f(0, 0); sendVertex(innn(i));

			glNormal3f(0, 0, 1);
			glTexCoord2f(0, 0); sendVertex(innp(i));
			glTexCoord2f(1, 0); sendVertex(ipnp(i));
			glTexCoord2f(1, 1); sendVertex(ippp(i));
			glTexCoord2f(0, 1); sendVertex(inpp(i));
			glEnd();
		}

		private {
			GLuint		_texture;
		}
	}


	class Ramp1Block : CubeBlock {
		this(GLuint texture)
		{
			super(texture);
		}

		override void render(uint i)
		{
			glBindTexture(GL_TEXTURE_2D, _texture);

			glBegin(GL_QUADS);
			glNormal3f(-1, 0, 0);
			glTexCoord2f(0, 0); sendVertex(innn(i));
			glTexCoord2f(1, 0); sendVertex(innp(i));
			glTexCoord2f(1, 1); sendVertex(inzp(i));
			glTexCoord2f(0, 1); sendVertex(inzn(i));

			glNormal3f(1, 0, 0);
			glTexCoord2f(0, 0.5); sendVertex(ipnn(i));
			glTexCoord2f(1, 0.5); sendVertex(ipnp(i));
			glTexCoord2f(1, 0); sendVertex(ipnp(i));
			glTexCoord2f(0, 0); sendVertex(ipnn(i));

			glNormal3f(0, -1, 0);
			glTexCoord2f(0, 0); sendVertex(innn(i));
			glTexCoord2f(1, 0); sendVertex(ipnn(i));
			glTexCoord2f(1, 1); sendVertex(ipnp(i));
			glTexCoord2f(0, 1); sendVertex(innp(i));

			glNormal3f(0, 1, 0);
			glTexCoord2f(0, 1); sendVertex(inzp(i));
			glTexCoord2f(1, 1); sendVertex(ipnp(i));
			glTexCoord2f(1, 0); sendVertex(ipnn(i));
			glTexCoord2f(0, 0); sendVertex(inzn(i));
			glEnd();

			glBegin(GL_TRIANGLES);
			glNormal3f(0, 0, -1);
			glTexCoord2f(0, 1); sendVertex(inzn(i));
			glTexCoord2f(1, 0); sendVertex(ipnn(i));
			glTexCoord2f(0, 0); sendVertex(innn(i));

			glNormal3f(0, 0, 1);
			glTexCoord2f(0, 0); sendVertex(innp(i));
			glTexCoord2f(1, 0); sendVertex(ipnp(i));
			glTexCoord2f(0, 1); sendVertex(inzp(i));
			glEnd();
		}
	}


	class Ramp2Block : CubeBlock {
		this(GLuint texture)
		{
			super(texture);
		}

		override void render(uint i)
		{
			glBindTexture(GL_TEXTURE_2D, _texture);

			glBegin(GL_QUADS);
			glNormal3f(-1, 0, 0);
			glTexCoord2f(0, 0); sendVertex(innn(i));
			glTexCoord2f(1, 0); sendVertex(innp(i));
			glTexCoord2f(1, 1); sendVertex(inpp(i));
			glTexCoord2f(0, 1); sendVertex(inpn(i));

			glNormal3f(1, 0, 0);
			glTexCoord2f(0, 0.5); sendVertex(ipzn(i));
			glTexCoord2f(1, 0.5); sendVertex(ipzp(i));
			glTexCoord2f(1, 0); sendVertex(ipnp(i));
			glTexCoord2f(0, 0); sendVertex(ipnn(i));

			glNormal3f(0, -1, 0);
			glTexCoord2f(0, 0); sendVertex(innn(i));
			glTexCoord2f(1, 0); sendVertex(ipnn(i));
			glTexCoord2f(1, 1); sendVertex(ipnp(i));
			glTexCoord2f(0, 1); sendVertex(innp(i));

			glNormal3f(0, 1, 0);
			glTexCoord2f(0, 1); sendVertex(inpp(i));
			glTexCoord2f(1, 1); sendVertex(ipzp(i));
			glTexCoord2f(1, 0); sendVertex(ipzn(i));
			glTexCoord2f(0, 0); sendVertex(inpn(i));

			glNormal3f(0, 0, -1);
			glTexCoord2f(0, 1); sendVertex(inpn(i));
			glTexCoord2f(1, 0.5); sendVertex(ipzn(i));
			glTexCoord2f(1, 0); sendVertex(ipnn(i));
			glTexCoord2f(0, 0); sendVertex(innn(i));

			glNormal3f(0, 0, 1);
			glTexCoord2f(0, 0); sendVertex(innp(i));
			glTexCoord2f(1, 0); sendVertex(ipnp(i));
			glTexCoord2f(1, 0.5); sendVertex(ipzp(i));
			glTexCoord2f(0, 1); sendVertex(inpp(i));
			glEnd();
		}
	}
}
